<?php
/**
 * Memcache索引方案
 * 暂时可能混合的特性比较多，以后再慢慢拆出来
 * 这些特性包括：
 * 索引按分类进行命名
 * 每个分类有独立的id与主表的id建立对应关系
 * 只支持按页（倒序）和顺序读取
 * 能支持分布
 * 目前该系统最大的困难是MYSQL端的分布问题
 *
 * @author ananda
 */
abstract class Pattern_Memcache_Index extends Pattern_Memcache
{
    /**
     * 传统数据库连接
     * !require!
     * @var <Connection_Pdo>
     */
    public $conn_db = null;

    /**
     * 数据库中的表名
     * !require!
     * @var <string>
     */
    public $table_name   = '';

    /**
     * 索引ID名
     * !require!
     * @var <string>
     */
    public $category_name = '';

    /**
     * 索引ID名
     * !require!
     * @var <string>
     */
    public $index_name = '';

    /**
     * 原始数据主键ID名
     * @var <string>
     */
    public $primary_name = 'id';

    /**
     * 在缓存中的索引序号前缀
     * @var <string>
     */
    public $prefix_seq   = 'tableindex_seq:';

    /**
     * 缓存前缀
     * @var <string>
     */
    public $prefix_cache = 'tableindex:';

    /**
     * 当前正在处理的分类名
     * @var string
     */
    public $category;

    /**
     * 构造器
     * @param <string> $category
     */
    public function __construct($category = '') {
        $this->setCategory($category);
    }

    /**
     * 设置分类
     * @param <string> $category 
     */
    public function setCategory($category)
    {
        $this->category = $category;
    }

    /**
     * 添加索引
     * @param integer $id
     * @return integer
     */
    public function append($id)
    {
        // 生成ID和KEY值
        $index  = $this->getIndexId($this->category);
        if (Error::is($index)) {
            return new Error('Get index id fail');
        }
        $key = $this->generateKey($index);

        // 往数据库中插入数据
        $sql    = $this->buildSqlAppend($index, $id);
        $result = $this->getDbConnection()->create($sql);
        if (Error::is($result)) {
            return $result;
        }

        // 成功的话，继续往缓存中插入数据
        $result = $this->getCacheConnection()->create($key, array($this->primary_name => $id));
        if (Error::is($result)) {
            return $result;
        }
        return $index;
    }

    public function get($key_list, $id_list, $sort)
    {
        // 防止没有数据的情况发生
        if (!count($key_list)) {
            return array();
        }

        $it = $this->getCacheConnection()->read($key_list);

        if (Error::is($it) || $it->rowCount() != count($key_list)) {
            // 数据不存在，重建
            // 读取MYSQL中的数据
            $sql = $this->buildSqlRead($id_list);
            if ($sort == Pattern_Memcache::ORDER_DESC) {
                $sql .= " ORDER BY $this->index_name DESC";
            } else {
                $sql .= " ORDER BY $this->index_name ASC";
            }
            $it = $this->getDbConnection()->read($sql);
            if (Error::is($it)) {
                return $it;
            }

            // 创建迭代器
            $it = new Pattern_Memcache_RebuildCache($it, $this, 'index_name');
        }
        return $it;
    }

    public function order($page = 1, $limit = 20, $sort = Pattern_Memcache::ORDER_DESC)
    {
        // 算出ID列表
        $page    = max($page, 1);
        $last_id = $this->lastIndexId();
        $list    = $this->getListId($last_id, $page, $limit, $sort);

        // 取数据
        return $this->get($list['key'], $list['id'], $sort);
    }

    public function buildSqlAppend($index, $id)
    {
        $sql = "INSERT INTO $this->table_name ($this->category_name, $this->index_name, $this->primary_name) VALUES (:category, :index, :id)";
        $sql = $this->getDbConnection()
                    ->replaceParams($sql,
                                    array('category' => $this->category, 'index' => $index, 'id' => $id),
                                    true);
        return $sql;
    }

    public function buildSqlRenewId()
    {
        $sql = "SELECT max($this->index_name) FROM $this->table_name WHERE $this->category_name = '$this->category'";
        return $sql;
    }

    /**
     * 创建读取数据库的sql
     * 可以优化使用LIMIT读取
     * @param <type> $list
     * @return <type>
     */
    public function buildSqlRead($list)
    {
        $temp1 = intval($list[0]);
        $temp2 = intval($list[count($list)-1]);
        if ($temp1 > $temp2) {
            $max = $temp1;
            $min = $temp2;
        } else {
            $min = $temp1;
            $max = $temp2;
        }
        $sql  = "SELECT $this->index_name, $this->primary_name FROM $this->table_name ".
                "WHERE $this->category_name = '$this->category' ".
                "AND $this->index_name >= $min AND $this->index_name <= $max";
        return $sql;
    }

    /**
     * 生成在MEMCACHE中存入的键值
     * @param <integer> $id
     * @return <string>
     */
    public function generateKey($index)
    {
        return $this->prefix_cache.$this->table_name.'/'.$this->category.'/'.$index;
    }

    /**
     * 获取序列号ID
     * @return <type>
     */
    public function sequenceKey()
    {
        return $this->prefix_seq.$this->table_name.'/'.$this->category;
    }

    /**
     * 重建ID值
     * @return <integer>
     */
    public function renewId()
    {
        $row = $this->getDbConnection()->readOne($this->buildSqlRenewId(), array(),
                                                 array('fetch_mode' => PDO::FETCH_BOTH));
        if (Error::is($row)) {
            return $row;
        }
        $id  = intval(max($row[0], 0));

        $result = $this->getCacheConnection()->update($this->sequenceKey(), $id);
        if (Error::is($result)) {
            return $result;
        }

        return $id;
    }

    /**
     * 取得最后插入的索引ID
     * @return <integer>
     */
    public function lastIndexId()
    {
        $id = $this->getCacheConnection()->readOne($this->sequenceKey());
        if (Error::is($id)) {
            $result = $this->renewId();
            if (Error::is($result)) {
                return $result;
            }
            $id = $this->getCacheConnection()->readOne($this->sequenceKey());
            if (Error::is($id)) {
                return $id;
            }
        }
        return intval($id);
    }

    /**
     * 取得要插入的索引ID
     * @return <integer>
     */
    protected function getIndexId()
    {
        $id = $this->getCacheConnection()->increment($this->sequenceKey());
        if (Error::is($id) || !$id) {
            $result = $this->renewId();
            if (Error::is($result)) {
                return $result;
            }
            $id = $this->getCacheConnection()->increment($this->sequenceKey());
            if (Error::is($id)) {
                return $id;
            }
        }
        return intval($id);
    }

    /**
     * 获取数据库连接对象
     * @return Connection_Pdo
     */
    public function getDbConnection()
    {
        if (!is_object($this->conn_db)) {
            $this->conn_db = Connection::get($this->conn_db);
        }
        return $this->conn_db;
    }
}
